package com.any.sckill.distributed.lock.zookeeper;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.apache.curator.framework.CuratorFramework;

import com.any.sckill.common.constants.SeckillConstants;
import com.any.sckill.common.utils.SerializeUtil;
import com.any.sckill.common.utils.StringUtil;
import com.any.sckill.common.utils.UuidUtil;
import com.any.sckill.distributed.zookeeper.CuratorProxy;
import com.any.sckill.distributed.zookeeper.ZKProxyFactory;
import com.any.sckill.distributed.zookeeper.ZookeeperProperties;
import com.any.sckill.exception.ExistZookeeperLockException;

public class ZookeeperLock {
	
	private CuratorProxy curatorProxy;
	
	private static final ConcurrentHashMap<String, ZookeeperLock> serviceMap = new ConcurrentHashMap<>();
	
	private ZookeeperLock(Builder builder) {
		// mutex node name in zk, use nodeName, serviceId or generated uuid if which is valid first 
		String zkNodeName = StringUtil.isValid(builder.getZookeeperProperties().getNodeName()) ? 
				builder.getZookeeperProperties().getNodeName() : 
					StringUtil.isValid(builder.getZookeeperProperties().getServiceId()) ? 
							builder.getZookeeperProperties().getServiceId() : 
								UuidUtil.getUuid();
		curatorProxy = Objects.isNull(builder.getClient()) ? ZKProxyFactory.getCuratorProxy(zkNodeName) : ZKProxyFactory.getCuratorProxy(builder.getClient(), zkNodeName);
		
		String mutexNodeName = curatorProxy.getMutexNodeName();
		serviceMap.put(mutexNodeName, this);
		
		builder.getZookeeperProperties().setServiceId(mutexNodeName);
		if (!StringUtil.isValid(builder.getZookeeperProperties().getServiceName())) {
			builder.getZookeeperProperties().setServiceName(SeckillConstants.DEFAULT_SERVICE_NAME);
		}
		Map<String, ZookeeperProperties> propertiesMap = null;
		propertiesMap = (Map<String, ZookeeperProperties>) SerializeUtil.deSerialize(curatorProxy.getData(SeckillConstants.DEFAULT_SERVICE_NODE_NAME));
		if (Objects.isNull(propertiesMap)) {
			propertiesMap = new HashMap<>();
			curatorProxy.createNode(SeckillConstants.DEFAULT_SERVICE_NODE_NAME, SerializeUtil.serialize(propertiesMap));
		}
		propertiesMap.put(builder.getZookeeperProperties().getServiceName(), builder.getZookeeperProperties());
		curatorProxy.setData(SeckillConstants.DEFAULT_SERVICE_NODE_NAME, SerializeUtil.serialize(propertiesMap));
	}
	
	public static Builder builder(String serviceName) throws ExistZookeeperLockException {
		if (!serviceMap.containsKey(serviceName)) {
			return new Builder(serviceName);
		} else {
			throw new ExistZookeeperLockException(serviceName + " is built. Do not build again");
		}
	}
	
	public static ZookeeperLock getZookeeperLock(String serviceName) {
		try {
			return builder(serviceName).build();
		} catch (ExistZookeeperLockException e) {
			return serviceMap.get(serviceName);
		}
	}
    
    /**
     * 加锁
     * @param time
     * @param unit
     * @return
     */
    public boolean acquire(long time, TimeUnit unit){
    	try {
			return curatorProxy.acquire(time, unit);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
    }
    
    /**
     * 释放锁
     */
    public void release(){
    	try {
    		curatorProxy.release();
		} catch (Exception e) {
			e.printStackTrace();
		}
    }
	
	public static class Builder {
		
		private ZookeeperProperties zookeeperProperties;
		private CuratorFramework client;
		
		public ZookeeperProperties getZookeeperProperties() {
			return zookeeperProperties;
		}

		public Builder setServiceId(String serviceId) {
			zookeeperProperties.setServiceId(serviceId);
			return this;
		}
		
		public Builder setZKNodeName(String nodeName) {
			zookeeperProperties.setNodeName(nodeName);
			return this;
		}

		public CuratorFramework getClient() {
			return client;
		}
		
		public Builder setCuratorClient(CuratorFramework client) {
			this.client = client;
			return this;
		}
		
		public ZookeeperLock build() {
			return new ZookeeperLock(this);
		}
		
		private Builder(String serviceName) {
			zookeeperProperties = new ZookeeperProperties();
			zookeeperProperties.setServiceName(serviceName);
		}
		
	}

}
